//Move Networks Template Player
//Copyright 2007 Move Networks, Inc.
// @author Tim Major
// @customized Ben Loveridge
// @revision 20070926-1515

/********************************************************************************************/
//                                                                                          //
//-----------------------------------------  MN  -------------------------------------------//
//                                                                                          //
/********************************************************************************************/
//The MN "namespace" is used by the SDK.  I have created a few functions under it when I    //
//thought it might be more appropriate to do so, i.e., they are used by several of the      //
//widgets and/or are more generic than just a "template player" function.  Otherwise, the   //
//MN.TP "namespace" is used to wrap the functions.                                          //
/********************************************************************************************/


//MN.SecToObj ------------------------------------------------------------------------------//
//Takes seconds and converts it into an object with hours, minutes, and seconds             //
//------------------------------------------------------------------------------------------//
MN.SecToObj = function(s){
    if (s < 0)
        s = 0;

    var h = Math.floor(s / 3600);
    s -= h * 3600;
    var m = Math.floor(s / 60);
    s -= m * 60;

    return {'hour':h, 'minute':m, 'second':s};
}

//MN.HourTo12Time---------------------------------------------------------------------------//
//Takes hours and converts it to a 12 hour time object, with the hours                      //
//and "am" or "pm".                                                                         //
//------------------------------------------------------------------------------------------//
MN.HourTo12Time = function(h){
    var am_pm = 'am';
    if(h >= 12) {
		if(h != 12)
			h = h - 12;
		am_pm = 'pm';
    }
    else{
		if(h == 0)
			h = 12;
    }
    return {'hour' : h, 'am_pm' : am_pm};
}

//MN.ConvertToTimestamp---------------------------------------------------------------------//
//This ConvertToTimestamp function takes either seconds or a date object with properties    //
//"hour", "minute", and "second" (the object returned from PosToDatetime *hint hint*) and   //
//formats it according to the formatString provided in the second parameter.  The string is //
//of the basic format "HH:MM:SS.SS", but can be changed to produce the desired timestamp.   //
//For example, "HH:MM" will only show hours and minutes; "MM:SS" will show minutes and      //
//seconds, etc.  For hours, you can use a single "H" to hide the leading zero ("HH" would   //
//produce "01", but "H" will just show "1").  You can also use a question mark ("?") after  //
//the "H"(s) to hide the hour slot if hours computes to zero ("HH" produces "00:MM", "HH?"  //
//produces "MM").  Putting a period (".") after seconds will display subsecond digits up to //
//as many "S"s that you add after the period ("MM:SS.S" produces "00:00:01.1",              //
//"MM:SS.SS" produces "00:00:01.11", etc.)  And finally, lowercase characters display 12    //
//hour time format, so use "hh:mm:ss" if you want am/pm.  Note that some options are not    //
//applicable to 12 hour time ("?", and there will never be leading zeros in the hour slot - //
//"HH" and "H" are the same).                                                               //
//                                                                                          //
//SPECIAL NOTE:  THERE IS ALREADY A FUNCTION NAMED "MN.ConvertToTimestamp" IN THE SDK THAT  //
//THIS OVERRIDES.  ALL SDK WIDGETS AND OBJECTS FOUND IN THE TEMPLATE PLAYER THAT USE        //
//MN.ConvertToTimestamp HAVE BEEN MODIFIED TO USE THIS VERSION                              //
//------------------------------------------------------------------------------------------//
MN.ConvertToTimestamp = function(arg, formatString){
    var ret = '';
    var is12 = false;
    var am_pm = '';
    var obj = null;

    if(typeof arg == 'number')
        obj = MN.SecToObj(arg);
    else if(typeof arg == 'object')
        obj = arg;
    else{
        //logError('MN.TP.ConvertToTimestamp: Invalid argument: ', arg);
        return;
    }

    if(formatString.charAt(0) >= 'a' && formatString.charAt(0) <= 'z'){ //lowercase, 12 hour time
        is12 = true;
    }

    formatString = formatString.toLowerCase();  //convert to lowercase to reduce number of conditionals
    var parts = formatString.split(':');
    for(var i = 0; i < parts.length; i++){
        if(parts[i].charAt(0) == 'h'){
            if(is12){
                //I don't think anybody is going to want to pad the hours when in 12 hour time
                ret += MN.HourTo12Time(obj.hour).hour + ':';
                am_pm = MN.HourTo12Time(obj.hour).am_pm;
            }

            //a '?' hides the hours if it is 0. Also, this option only applies to 24 hour format;
            //you will never have "0 o'clock" with 12 hour time
            else if(parts[i].indexOf('?') == -1 || obj.hour > 0){
                ret += parts[i].charAt(1) == 'h' ? MN.PadDigits(obj.hour, 2) : obj.hour; //if it is 'hh', pad the digits
                ret += ':';
            }
        }
        else if(parts[i].charAt(0) == 'm'){
            if(parts[i].charAt(1) == 'm' || formatString.indexOf('h') != -1) //if timestamp has hours, pad minutes no matter what
                ret += MN.PadDigits(obj.minute, 2)
            else
                ret += obj.minute;
            ret += ':';
        }
        else if(parts[i].charAt(0) == 's' && parts[i].charAt(1) == 's'){ //can be 'ss' or 'ss.s', 'ss.ss', etc.
            var subSec = parts[i].split('.')[1];
            if(subSec)
                ret += MN.PadDigits(obj.second.toFixed(subSec.length), subSec.length + 2);
            else{
                var sec = MN.PadDigits(obj.second.toFixed(0), 2);
				if (sec == 60)
					sec = 59;
				ret += sec;
			}
            ret += ':';
        }
    }
    //chop off the extra ':'
    ret = ret.slice(0,-1);
    ret += am_pm;
    return ret;
}

//MN.PopulateTemplate-----------------------------------------------------------------------//
//This function is used for both the playlist and the show timeline.  It takes a DOM element//
//(template) and populates it with data from the showObj (which in these two cases          //
//originates from a qvt) by finding special class names in the element and its children.    //
//------------------------------------------------------------------------------------------//

MN.PopulateTemplate = function(template, showObj, posToDatetime){
    var newBox = template.cloneNode(true);
    var curElement;
    for(var curProp in showObj){
        if(curProp == 'tlStartTime' || curProp == 'tlStopTime')
            curProp = curProp.replace('tlS', 's');

        //append "mn_show" to the property and search for and element with that class
        elements = MN.GetElementsByClassName(newBox, 'mn_show_' + curProp, true);
        if(curProp == 'startTime' || curProp == 'stopTime' || curProp == 'duration'){ //these three use timestamps
            var timeFormat;
            var ts;
            if(curProp == 'startTime' || curProp == 'stopTime')
                curProp = 'tlS' + curProp.slice(1);  //the property in the showObj is tlStartTime or tlStopTime

            for(var i = 2; i < elements.length; i++){
                curElement = elements[i];
                timeFormat = curElement.innerHTML || 'HH:MM:SS';

                //force to 24 hour timestamp when necessary.  The absence of posToDatetime
                //means that the content is "vod", and so we force a 24 hour timestamp
                if(curProp == 'duration' || !posToDatetime)
                    timeFormat = timeFormat.toUpperCase();

                if(posToDatetime)
                    ts = MN.ConvertToTimestamp(posToDatetime(showObj[curProp]), timeFormat);
                else
                    ts = MN.ConvertToTimestamp(showObj[curProp], timeFormat);

                //curElement.innerHTML = ts;
                MN.SetInnerText(curElement,ts);
            }
        }
        else if(curProp == 'thumbnail'){
            for(var i = 0; i < elements.length; i++){
                //elements[i].innerHTML += '<img src="' + showObj[curProp] + '" />';
                MN.SetInnerText(elements[i],elements[i].innerHTML + '<img src="' + showObj[curProp] + '" />');
            }
        }
        else{
            for(var i = 0; i < elements.length; i++){
                //elements[i].innerHTML += showObj[curProp];
                MN.SetInnerText(elements[i],elements[i].innerHTML + showObj[curProp]);
            }
        }
    }

    return newBox;
}

//MN.GetElementsByClassName-----------------------------------------------------------------//
//This function searches an element's children for a specific class name, and returns an    //
//array of those elements.  The third option, recursive, is a boolean flag that tells the   //
//function to search the children's children, and the children's childrens' children, etc.  //
//------------------------------------------------------------------------------------------//
MN.GetElementsByClassName = function(element, className, recursive){
    var array = [];
    if(element.hasChildNodes()){
        var child;
        for(var i = 0; i < element.childNodes.length; i++){
            child = element.childNodes[i];
            if(child.nodeType == 1){ //make sure it is not a text node
                //I don't think the order matters much
                if(child.className == className)
                    array.push(child);
                if(recursive)
                    array = array.concat(this.GetElementsByClassName(child, className, recursive));
            }
        }
    }
    return array;
}


/********************************************************************************************/
//                                                                                          //
//----------------------------------------  MN.TP  -----------------------------------------//
//                                                                                          //
/********************************************************************************************/
//The MN.TP class is used to protect the template player functions from possibly      		//
//conflicting with other things on the page.                                                //
/********************************************************************************************/


//------------------------------------------------------------------------------------------//
//-------------------------------  Variables and Constants ---------------------------------//
//------------------------------------------------------------------------------------------//
MN.TP = {};

MN.TP.onLive = null;
MN.TP.disableLive = false;
 //the parameters passed to this page (obtained by calling MN.GetPageParams in the
 //MN.TP.WindowLoaded function)
MN.TP.params;
//the player object reference
MN.TP.qmp = null;
//The template player works based on a configuration file, config.js, that sets up a few
//data structures that are used to configure the page behavior.  The most atomic of these
//is a "channel".  MN.TP.curChnl stores the channel that we are on so we can reference the
//configuration.
MN.TP.curChnl = null;
MN.TP.curShow = null;
MN.TP.maximized = false;
MN.TP.resizable = false;
MN.TP.firstTl = true;

MN.TP.startPos = -1;
MN.TP.stopPos = -1;
MN.TP.showChangedOnClick = false;
MN.TP.curURL = null;
MN.TP.customStartStop = true;
MN.TP.browser = null;
MN.TP.screenWidth = null;
MN.TP.screenHeight = null;

MN.TP.MAX_FFORWARD_SPEED = 16;
MN.TP.MAX_REWIND_SPEED = -16;
MN.TP.LIVE_THRESHOLD = 90;

//MN.TP.SetStatusMsg------------------------------------------------------------------------//
//Sets the status message area with a particular message.  									//
//------------------------------------------------------------------------------------------//
MN.TP.SetStatusMsg = function(msg){
	//find the elements with a special class, "mn_player_status"
    //var elements = MN.GetElementsByClassName(document.body, 'mn_player_status', true);
    var elements = MN.GetElementsByClassName($('mn_player_container'), 'mn_player_status', true);
    for(var i = 0; i < elements.length; i++){
        MN.SetInnerText(elements[i], msg);
    }
}

//MN.TP.PopupWin----------------------------------------------------------------------------//
//This is called when a user clicks on an anchor that has been set as a "popup" via			//
//MN.TP.InitPopups.  It opens a new window to the link.										//
//------------------------------------------------------------------------------------------//
MN.TP.PopupWin = function(){
    //the function has been bound via MN.MakeBound, so "this" references the anchor element
    var width = this.getAttribute('popup_width') || 650;
    var height = this.getAttribute('popup_height') || 664;
	var scroll = this.getAttribute('popup_scroll') != 'false' ? 'yes' : 'no';
	var resize = this.getAttribute('popup_resize') != 'false' ? 'yes' : 'no';
	var title = this.getAttribute('popup_title') ? this.getAttribute('popup_title').replace(/ /g,'_') : 'popup';
    window.open(this.getAttribute('href'),title,'width=' + width + ',height=' + height + ',status=yes,scrollbars=' + scroll + ',resizable=' + resize + ',location=no,toolbar=no');
}


//------------------------------------------------------------------------------------------//
//--------------------------------  Player Control Functions  ------------------------------//
//------------------------------------------------------------------------------------------//
//These functions are used to control the player, and are called by the buttons on the page.//
//------------------------------------------------------------------------------------------//

//The back button behaves similar to the back button on a DVD player.  When first clicked, it
//will restart the current show.  If pressed again within a certain amount of time, it
//will start skipping back shows.  The following two variables are used to maintain that
//behavior
MN.TP.skipping = false;
MN.TP.skipTimer = null;
//MN.TP.Back--------------------------------------------------------------------------------//
//Called to skip back a show in the timeline.  If the current channel has an epg (meaning  	//
//there is more than just one timeline available to view) check to see if there is a       	//
//"previous" timeline, if so, skip to the last show of that timeline                       	//
//------------------------------------------------------------------------------------------//
MN.TP.Back = function(){
		if(MN.TP.onLive)
			return;
    log('back');
    var showNum = MN.TP.qmp.CurrentShow();
    var qvt = MN.TP.qmp.CurrentQVT();
    if(showNum < 0)
        showNum = qvt.ShowCount() - 1;

    if(!MN.TP.skipping){ //restart the show
        MN.TP.qmp.CurrentPosition(qvt.StartTime(showNum));
        MN.TP.skipping = true;
        MN.TP.skipTimer = setTimeout('MN.TP.skipping = false', 5000);
    }
    else{ //skip back a show
        clearTimeout(MN.TP.skipTimer);

        if(MN.TP.curChnl._epg && showNum == 0){ //we have more than one day of content, and are on the first show
            var prevURL = qvt.PrevURL();
            if(prevURL){
                //see if the previous timeline is in the epg, and therefore valid to skip back to.
                //by the time a user gets to this point, the previous url will have been loaded into the
                //epg if it is valid (the epg loads fairly quickly), so if prevURL is not in the epg
                //it is assumed as invalid, not unloaded.
                for(var i = 0; i < MN.TP.curChnl._epg.urlList.length; i++){
                    if(MN.TP.curChnl._epg.urlList[i] == prevURL){
                        MN.Event.Observe(MN.TP.qmp, 'TimelineLoaded', MN.TP.SkipToLastShow);
		                MN.TP.qmp.Load(prevURL);
		                return;
                    }
                }
            }
        }
        else{
            MN.TP.qmp.CurrentShow(showNum - 1);
        }

        MN.TP.skipTimer = setTimeout('MN.TP.skipping = false', 5000);
    }
}

//MN.TP.SkipToLastShow----------------------------------------------------------------------//
//Skips to the last show of a qvt.  This is used in conjunction with the MN.TP.Back function//
//------------------------------------------------------------------------------------------//
MN.TP.SkipToLastShow = function(qvt){
	var numOfShows = qvt.ShowCount();
	MN.TP.qmp.CurrentShow(numOfShows - 1);
	MN.Event.StopObserving(MN.TP.qmp, 'TimelineLoaded', MN.TP.SkipToLastShow);
}


//The following two variables are used with the MN.TP.Rewind and MN.TP.FastForward functions
MN.TP.scrubRate = 0;
//States:
// -1 ready to scrub
//  0 starting to scrub
// >0 preparing to scrub
MN.TP.scrubState = -1;

//MN.TP.Rewind------------------------------------------------------------------------------//
//The rewind button works much like the rewind function of a DVD player.  You can click it	//
//multiple times to obtain different rewind rates.  In this case, the rates double each time//
//they are clicked, up to MN.TP.MAX_REWIND_SPEED.  Both the MN.TP.Rewind and				//
//MN.TP.FastForward functions work on a "queue" system.  Because of limitations in the		//
//player, calls to player.Scrub should not be made rapidly.  To overcome this, every time	//
//this function is called, it queues up a timeout to call the MN.TP.DoScrub function (which	//
//actually makes the call to player.Scrub).  This timeout is set for 500 milliseconds. When	//
//MN.TP.DoScrub is actually called, it checks to see if there has been a more recent call	//
//queued up.  This queue allows the user to click on the rewind and fast forward buttons as	//
//many times as they like without causing problems with the player.							//
//------------------------------------------------------------------------------------------//
MN.TP.Rewind = function(){
		//if(MN.TP.onLive)
		//	return;
		if($('mn_rewind').className.search('disabled') != -1 || MN.TP.qmp.CurrentPosition() < MN.TP.startPos)
			return;
		if(MN.TP.qmp.Paused())
			MN.TP.qmp.Paused(false); //if rewind started during pause, doesn't always resume correctly; could also call MN.TP.Play()
    if(MN.TP.scrubState < 0)
        MN.TP.scrubState = 1;
    else if(MN.TP.scrubState == 0)
        return;
    else
        MN.TP.scrubState++;

    if(MN.TP.scrubRate >=0 || (MN.TP.scrubRate * 2) < MN.TP.MAX_REWIND_SPEED)
			MN.TP.scrubRate = -2;
		else
			MN.TP.scrubRate *= 2;

    log('attempting to scrub at rate: ', MN.TP.scrubRate, 'x');
    MN.TP.SetStatusMsg('Regresar: ' + MN.TP.scrubRate + 'x');
    setTimeout('MN.TP.DoScrub(' + MN.TP.scrubRate +')', 500);
}

//MN.TP.Pause-------------------------------------------------------------------------------//
//Pauses the player and sets the appropriate status message.								//
//------------------------------------------------------------------------------------------//
MN.TP.Pause = function(){
		if($('mn_pause').className.search('disabled') != -1)
			return;
    log('pause');
    MN.TP.qmp.Paused(true);
    MN.TP.SetStatusMsg('Pausa');
}

//MN.TP.Play--------------------------------------------------------------------------------//
//Called after a pause or while scrubbing to start normal playback							//
//------------------------------------------------------------------------------------------//
MN.TP.Play = function(){
	if($('mn_play').className.search('disabled') != -1) //don't play if the button's class has 'disabled'
		return;
	log('play');
	if(MN.TP.qmp.scrubbing || MN.TP.scrubRate != 0)
	{
		MN.TP.qmp.StopScrubbing();
		MN.TP.scrubRate = 0;
	}
	MN.TP.qmp.Paused(false);
	//if they somehow got to a previous show and paused, resume playback at the appropriate point
	if(MN.TP.qmp.CurrentPosition() < MN.TP.startPos)
			MN.TP.qmp.CurrentPosition(MN.TP.startPos);
	var br = MN.TP.qmp.CurrentBitRate();
	if(br > 0)
		MN.TP.SetStatusMsg('Conexi\u00f3n: ' + br + 'kbps');
  else
    MN.TP.SetStatusMsg('Conectado ');
}

//MN.TP.FastForward-------------------------------------------------------------------------//
//See MN.TP.Rewind																			//
//------------------------------------------------------------------------------------------//
MN.TP.FastForward = function(){
    //don't ff if they're past the end of the show, button is disabled, or within 30 seconds of live content
    if((!MN.TP.onLive && MN.TP.stopPos != -1 && MN.TP.qmp.CurrentPosition() > MN.TP.stopPos-10) || $('mn_fast_forward').className.search('disabled') != -1 || (MN.TP.qmp.Duration() - MN.TP.qmp.CurrentPosition() < 30))
		return;
	if(MN.TP.qmp.Paused())
		MN.TP.qmp.Paused(false);

	if(MN.TP.scrubState < 0)
        MN.TP.scrubState = 1;
    else if(MN.TP.scrubState == 0)
        return;
    else
        MN.TP.scrubState++;

    if(MN.TP.scrubRate <=0 || (MN.TP.scrubRate * 2) > MN.TP.MAX_FFORWARD_SPEED)
		MN.TP.scrubRate = 2;
	else
		MN.TP.scrubRate *= 2;

    log('attempting to scrub at rate: ', MN.TP.scrubRate, 'x');
    MN.TP.SetStatusMsg('Adelantar: ' + MN.TP.scrubRate + 'x');
    setTimeout('MN.TP.DoScrub(' + MN.TP.scrubRate + ')', 500);
}

//MN.TP.Forward-----------------------------------------------------------------------------//
//Skips forward a show in the timeline.  If we are on the last show of the timeline, and the//
//current channel has an EPG, the EPG is checked to see if there is a "next" timeline.  If	//
//so, the next timeline is played.															//
//------------------------------------------------------------------------------------------//
MN.TP.Forward = function(){
    if(MN.TP.onLive)
			return;
		log('forward');
    var showNum = MN.TP.qmp.CurrentShow();
    var qvt = MN.TP.qmp.CurrentQVT();
    if(showNum < 0)
        showNum = qvt.ShowCount() - 1;

    if(MN.TP.curChnl._epg && showNum == (qvt.ShowCount() - 1)){ //there are more timelines, and we are on the last show
        var nextURL = qvt.NextURL();
        if(nextURL){
            for(var i = 0; i < MN.TP.curChnl._epg.urlList.length; i++){
                if(MN.TP.curChnl._epg.urlList[i] == nextURL){
					//the playlist ignores "live" qvts when they reload (playlist.js _LoadQVT), and due
					//to that, we need to explicitly tell the playlist to load when it is an okay instance
					//like this
					if(MN.TP.qmp.playlist){
						MN.Event.Observe(MN.TP.qmp, 'TimelineLoaded', MN.TP.LiveTlLoaded)
					}
					log('forward, switching urls')
					 MN.TP.qmp.Play(nextURL, 0);
                    return;
                }
            }
        }
    }
    else{
        if(showNum == qvt.ShowCount() - 2)
					MN.TP.GoLive();
				else
					MN.TP.qmp.CurrentShow(showNum + 1);
    }

}

MN.TP.LiveTlLoaded = function(qvt){
	MN.Event.StopObserving(MN.TP.qmp, 'TimelineLoaded', MN.TP.LiveTLLoaded);
	log('live tl loaded');
	if(qvt.IsOpenEnded())
		MN.TP.qmp.playlist._Populate(qvt);
}


//MN.TP.DoScrub-----------------------------------------------------------------------------//
//This function is called by a timeout set up by the MN.TP.Rewind and MN.TP.FastForward		//
//functions in order to actually make the call to the player to scrub.  By checking the		//
//MN.TP.scrubState variable, the function determines if another call to scrub was queued up	//
//during the timeout.  If there was, then the current scrub call is ignored and the queue 	//
//starts to empty.  When the last queued call to scrub is made, it actually calls			//
//MN.TP.qmp.Scrub with the appropriate rate.												//
//------------------------------------------------------------------------------------------//
MN.TP.DoScrub = function(rate){
    MN.TP.scrubState--;
    //another scrub call was queued up, ignore this one
    if(MN.TP.scrubState > 0)
        return;
    //allow user to queue up more scrubbing
    MN.TP.scrubState = -1;
    if(MN.TP.qmp.Scrub(rate)){
        log('sucessful scrub at rate: ', rate);
        MN.TP.SwapPausePlay(true);

    }
    else{
        log('scrub unsuccessful at rate: ', rate);
				var msg = rate > 0 ? 'Adelantar ' : 'Regresar ';
        MN.TP.SetStatusMsg('Incapaz ' + msg);
        MN.TP.scrubRate = 0;

        if(MN.TP.qmp.Paused()){
            setTimeout(function(){
                MN.TP.SetStatusMsg('Pausa');
            }, 3000);
        }
        else{
            setTimeout(function(){
							MN.TP.SetStatusMsg('Conexi\u00f3n: ' + MN.TP.qmp.CurrentBitRate() + 'kbps');
            }, 3000);
        }
    }
}

//MN.TP.GoLive------------------------------------------------------------------------------//
//Sets playback at "live".  This could be used with VOD content, but would just skip to the	//
//end of the content, so hopefully the user is smart enough not to have a go live button for//
//VOD content.  Also, the convention is that MN.TP.curChnl.url is "today's" url, so if the 	//
//current channel has an epg, we check to see if we need to load today's url and start      //
//playback at the end of that "live" url.                                                   //
//------------------------------------------------------------------------------------------//
MN.TP.GoLive = function(){
    if(MN.TP.disableLive)
    	return;
	log('go live');
	MN.TP.customStartStop = true;
	MN.TP.stopPos = -1;
	MN.TP.onLive = true;
	//MN.TP.Play();
	MN.TP.qmp.playlist._boxTemplate = MN.TP.curChnl.playlist.template;
	var position = MN.TP.qmp.CurrentPosition();
	var duration = MN.TP.qmp.CurrentQVT().Duration();
	var todaysQVT = MN.QVT.AcquireQVT(MN.TP.curChnl.url);
	if(MN.TP.curChnl._epg){
	    if(todaysQVT != MN.TP.qmp.CurrentQVT()){
		    $('mn_content_dropdown').options[0].selected = true;
			log('going live and switching qvt');
		    MN.TP.qmp.Play(todaysQVT, todaysQVT.Duration());
			if(MN.TP.qmp.playlist){
				MN.TP.qmp.playlist._Populate(todaysQVT);
			}
			//$('mn_content_dropdown').options[0].selected = true;
			return;
        }
        else{
			var showBoxes = MN.TP.qmp.playlist._canvas.childNodes;
	        for(var i = 0; i < showBoxes.length; i++){
				if(showBoxes[i].getAttribute("playing") == "true"){
					log('remove highlight: ',i)
					showBoxes[i].className = showBoxes[i].className.replace('mn_highlight', '');
					showBoxes[i].setAttribute("playing","false");
				}
			}
		}
	}
	//if playback is paused OR more than MN.TP.LIVE_THRESHOLD seconds behind "live", go live; otherwise, don't bother
	if(MN.TP.qmp.Paused() || (duration - position > MN.TP.LIVE_THRESHOLD)){
	    log('going live');
	    MN.TP.qmp.CurrentPosition(duration);
	    //MN.TP.qmp.Play(todaysQVT.GetSource(), todaysQVT.Duration());
	    return;
	}
}


//MN.TP.Maximize----------------------------------------------------------------------------//
//Switches the page to fullscreen.  This is tailored to the HTML.  There are basically two	//
//methods to get a fullscreen player.  One is to explicitly set the width and height of the	//
//player whenever the window resizes (via the window.OnResize event), or the HTML can		//
//carefully be crafted so that the player container resizes naturally with the window.  The	//
//player width and height can then be set to 100%, and will automatically resize to fit its	//
//container (which has been set to a percentage value, thus resizing with the page).  The 	//
//latter is easier from a javascript standpoint, however, in our experience	it has been a 	//
//bit tricky to get the HTML correct to display the player when its height is set to a 		//
//percentage (the player disappears).  In this implementation, we have gotten the height to	//
//behave correctly, and so we set the player width and height to 100%, and then	control the //
//player container ("mn_player") size to do the things we need, such as adjusting the player//
//height for aspect ratio.  This also allows the CSS to control the size of the player, 	//
//separating look from function. 															//
//------------------------------------------------------------------------------------------//
MN.TP.FullWarning = false;
MN.TP.Maximize = function(evt){

	if(!MN.TP.FullWarning)
	MN.TP.qmp.fullScreen(true);

	/*if(MN.TP.maximized)
		return;

	window.moveTo(0,0);
	window.resizeTo(screen.availWidth, screen.availHeight);

	MN.TP.SetToResizable(evt);

	MN.TP.maximized = true;
	MN.TP.OnResize();

	if(!MN.nonIE){
		window.resizeTo(screen.availWidth, screen.availHeight-1);
		MN.TP.OnResize();
		window.resizeTo(screen.availWidth, screen.availHeight);
		MN.TP.OnResize();
	}*/
}

MN.TP.SetToResizable = function(evt){
	evt = evt || window.event;
	evt.cancelBubble = true;

	var hiddenArray = ['mn_playlist_container', 'mn_categories', 'mn_link', 'mn_header', 'copyright', 'shows', 'episodes_container', 'logo', 'links'];
    for(var i = 0; i < hiddenArray.length; i++){
        if($(hiddenArray[i]))
            $(hiddenArray[i]).className = 'mn_hidden';
    }

    var maximizeArray = ['mn_public_page', 'mn_controller', 'mn_player_container', 'mn_container'];
    for(var i = 0; i < maximizeArray.length; i++){
        if($(maximizeArray[i]))
            $(maximizeArray[i]).className = 'mn_maximized';
    }

	MN.TP.resizable = true;
}


//MN.TP.NormalSize----------------------------------------------------------------------------//
//Returns the page from fullscreen mode.													//
//------------------------------------------------------------------------------------------//
MN.TP.NormalSize = function(evt){
    log('normal size');

    var hiddenArray = ['mn_playlist_container', 'mn_categories', 'mn_link', 'mn_header', 'copyright', 'shows', 'episodes_container', 'logo', 'links'];
    for(var i = 0; i < hiddenArray.length; i++){
        if($(hiddenArray[i]))
            $(hiddenArray[i]).className = 'mn_visible';
    }

    var minimizeArray = ['mn_public_page', 'mn_controller', 'mn_player_container', 'mn_container'];
    for(var i = 0; i < minimizeArray.length; i++){
        if($(minimizeArray[i]))
            $(minimizeArray[i]).className = '';
    }

	MN.TP.maximized = false;
	MN.TP.resizable = false;

	window.resizeTo(MN.TP.screenWidth,MN.TP.screenHeight);

	MN.TP.OnResize();
	if(!MN.nonIE){
		window.resizeTo(MN.TP.screenWidth+1,MN.TP.screenHeight+1);
		MN.TP.OnResize();
		window.resizeTo(MN.TP.screenWidth,MN.TP.screenHeight);
		MN.TP.OnResize();
	}
}

MN.TP.Minimize = function(evt){
	if(MN.TP.maximized)
		return;

	//window.moveTo(0,0);
	window.resizeTo(500,455);

	MN.TP.SetToResizable(evt);

	var minimizeArray = ['mn_public_page', 'mn_controller', 'mn_player_container', 'mn_container'];
    for(var i = 0; i < minimizeArray.length; i++){
        if($(minimizeArray[i]))
            $(minimizeArray[i]).className += ' mn_compact';
    }

	MN.TP.maximized = true;
	MN.TP.OnResize();
	if(!MN.nonIE){
		window.resizeTo(690,350);
		MN.TP.OnResize();
		window.resizeTo(690,350);
		MN.TP.OnResize();
	}
}
/*MN.TP.Minimize = function(evt){
	window.moveTo(0,0);
	window.resizeTo(250, 250);

	$('mn_back').style.display = 'none';
	$('mn_rewind').style.display = 'none';
	$('mn_fast_forward').style.display = 'none';
	$('mn_forward').style.display = 'none';
	$('mn_go_live').style.display = 'none';

	$('mn_timeline').style.display = 'none';
	$('mn_volume_bar').style.display = 'none';

	MN.TP.SetToResizable(evt);

	$('resizer1').style.display = 'none';
	$('mn_mini').style.display = 'none';
	$('mn_revert').style.display = 'block';
	$('mn_normal_size').style.display = 'none';

	MN.TP.maximized = false;
	MN.TP.OnResize();
}*/

//MN.TP.OnResize----------------------------------------------------------------------------//
//This is called whenever the window resizes.												//
//------------------------------------------------------------------------------------------//
MN.TP.OnResize = function(){
	if(!MN.TP.resizable){
		$('mn_player').style.height = '100%';
		//$('mn_controller').style.width = '480px';
	}
	else{
  	//unfortunately, the HTML/CSS structure of the page requires us to
		//calculate the height of the player when in fullscreen
		var border = $('mn_show_title').offsetHeight + $('mn_controller').offsetHeight;
		var windowHeight = MN.GetWindowSize()[1];

		$('mn_player').style.height = (windowHeight - border) + 'px';
		$('mn_controller').style.width = '100%';
	}
	var newTlWidth = $('mn_controller').offsetWidth - ($('mn_controller_right').offsetWidth);
    if(newTlWidth > 0)
    	$('mn_timeline').style.width = newTlWidth+ 'px';


}

//------------------------------------------------------------------------------------------//
//-----------------------------------  Popout functions  -----------------------------------//
//------------------------------------------------------------------------------------------//
//These functions are used to create and maintain the "popout" (formerly known as			//
//"tearaway") player.																		//
//------------------------------------------------------------------------------------------//

//MN.TP.OpenPopout--------------------------------------------------------------------------//
//Opens the popout player.  The popout player is a seperate, simplified HTML page that		//
//contains only a maximized player.  This page is opened in a new window, and in addition to//
//the links that exist between a window and its opener, a unique id is created and stored on//
//both pages, so we know when the specific instance of the opening page is no longer		//
//available to "pop in" to (either through the browser window closing, or the user 			//
//navigating to a different page).  If the original page still exists when the popout is 	//
//closed, the popout transfers video playback back.  The popout uses this script, so to 	//
//start playback at the appropriate place, we simply use a send-to-friend url.				//
//------------------------------------------------------------------------------------------//
MN.TP.OpenPopout = function(){
    log('open popout');
    var paused = MN.TP.qmp.Paused();
    MN.TP.qmp.Stop();
    var start = MN.TP.qmp.CurrentPosition() || -1;
    var url = MN.TP.qmp.CurrentQVT().PrimaryURL() || MN.TP.curChnl.url;
    var href = 'popout.html';

    //create a unique "id" for this page, and pass it to the popout, so that we can correctly
    //close the popout and restore this page (see if the user has remained on this page)
    var date = new Date();
    var id = date.getTime();
    $('mn_page_id').setAttribute('page_id', id);

	//open up popout.html with a send-to-friend url.  Also pass the current player volume and
	//the pause state so they are transfered.
    var params = {'popout' : true, 'openerID' : id, 'chnl': MN.TP.curChnl.name, 'url': url, 'start': start,
					'stop': '-1', 'vol' : MN.TP.qmp.Volume()};
    if(paused)
        params.paused = true;
    if(MN.TP.params.debug)
        params.debug = true;
    var ref = MN.URL.SetParams(href, params);
   	window.open(ref, 'popout', 'width=650,height=664,status=no,scrollbars=yes,resizable=yes,location=no,toolbar=no');
	//window.open('http://www.byu.tv', 'popout', 'width=650,height=664,status=no,scrollbars=yes,resizable=yes,location=no,toolbar=no');

	//hide the player on the page
    $('mn_player_container').style.visibility = 'hidden';
}

//MN.TP.ClosePopout-------------------------------------------------------------------------//
//Called by the popout player when it closes, or the user clicks the "pop in" button.  As	//
//mentioned in MN.TP.OpenPopout above, it checks, via a shared unique id, to see if the		//
//original opening document exists; if it does, then it transfers the video back.			//
//------------------------------------------------------------------------------------------//
MN.TP.ClosePopout = function(){
    log('PAGE: closing popout');
    if(window.opener && !window.opener.closed){
        var openerDoc = window.opener.document;
        var popoutID = MN.TP.params.openerID;
        try{
            if(openerDoc.getElementById('mn_page_id')){
                var openerID = openerDoc.getElementById('mn_page_id').getAttribute('page_id');
                if(openerID == popoutID){
                    log('PAGE: popout: opening page is still open, returning to it');
                    var url = MN.TP.qmp.CurrentQVT().PrimaryURL();
                    var pos = MN.TP.qmp.CurrentPosition();
                    openerDoc.getElementById('mn_player_container').style.visibility = 'visible';

                    //transfer status back to the opener
                    if(MN.TP.qmp.Paused())
                        window.opener.MN.Event.Observe(window.opener.MN.TP.qmp, 'PlayStateChanged', window.opener.MN.TP.TransferPaused);
                    window.opener.MN.TP.qmp.Volume(MN.TP.qmp.Volume());
                    window.opener.MN.TP.qmp.Play(url, pos);
                }
            }
        }
        //error commonly caused by trying to access openerDoc.getElementById when the user has changed
        //the page in the opener
        catch (e){}
    }

    window.close();
}


//MN.TP.InitWidgets-------------------------------------------------------------------------//
//Sets up the "widgets" on the page.  They are the volume bar, playlist, "traditional"		//
//timeline, and "show" timeline.  The timelines are very customized for this page (see		//
//timelines.js), and more information about the playlist can be found in playlist.js.  These//
//widgets use "time format" strings (as detailed in MN.ConvertToTimestamp) and HTML			//
//"templates" to populate.  A template is an DOM element that is populated with information	//
//based on special classes assigned to the element and its children.						//
//------------------------------------------------------------------------------------------//
MN.TP.InitWidgets = function(){
	log('PAGE: Initializing widgets');

    //volume bar
    if($('mn_volume_bar')){
        MN.TP.qmp.volumebar = new MN.PlayerUI.VolumeBar(MN.TP.qmp, 'mn_volume_bar');
    }
	log('PAGE: volume bar');

    //Playlist
    if($('mn_playlist')){
        MN.TP.qmp.playlist = new MN.Playlist(MN.TP.qmp, 'mn_playlist', 'mn_content_dropdown', 'mn_pl_scroller');
        MN.TP.InitPlEvents();
    }
	log('PAGE: playlist')

    //"Traditional" timeline
    if($('mn_timeline')){
        var scrubber = $('mn_timeline_scrubber');
        MN.TP.qmp.timeline = new MN.PlayerUI.TraditionalTimeline(MN.TP.qmp, 'mn_timeline', {'posdur':'mn_duration'});
        //this is a workaround to get the position to appear with the timeline scrubber "thumb"
        //a change needs to be made to the SDK to eliminate the need for this workaround
        // var newDiv = document.createElement('div');
        //        newDiv.id = 'mn_scrubber_position';
        //        newDiv.className = 'mn_hidden';
        //        scrubber.appendChild(newDiv);
        //        MN.TP.qmp.timeline.scrubPosID = 'mn_scrubber_position';
        //        MN.Event.Observe($('mn_timeline'), 'mousedown', function(){$('mn_scrubber_position').className = 'mn_visible';});
        //        MN.Event.Observe(document, 'mouseup', function(){$('mn_scrubber_position').className = 'mn_hidden';});
    }

	MN.TP.AdjustWidgets(MN.TP.curChnl)
	log('PAGE: done initializing widgets');
}

MN.TP.AdjustWidgets = function(chnl){
	if($('mn_playlist'))
		MN.TP.qmp.playlist._boxTemplate = chnl.playlist.template;

	if($('mn_timeline')){
		if(chnl.timeline != 'hidden'){ //quaranteed to be non-null after MN.TP.InitChannels()
	        MN.TP.qmp.timeline.posFormat = chnl.timeline.posFormat;
	        MN.TP.qmp.timeline.durFormat = chnl.timeline.durFormat;
	        MN.TP.qmp.timeline.scrubFormat = chnl.timeline.scrubFormat;
	        if(chnl.timeline.type == 'traditional')
	            MN.TP.TradTimelineMode(MN.TP.qmp.timeline);
	        else
	            MN.TP.ClipTimelineMode(MN.TP.qmp.timeline);
	    }
	}
}


//MN.TP.InitPlEvents------------------------------------------------------------------------//
//Initializes the events that are associated with the playlist widget (see playlist.js for 	//
//more info).																				//
//------------------------------------------------------------------------------------------//
MN.TP.InitPlEvents = function(){
    MN.Event.Observe(MN.TP.qmp.playlist, 'PageChanged', MN.TP.PlPageChanged);
    MN.Event.Observe(MN.TP.qmp.playlist, 'Populated', MN.TP.PlPopulated);

	//hook up the playlist page up and playlist page down buttons
    var prevBtns = MN.GetElementsByClassName(document.body, 'mn_pl_prev_page', true);
    for(var i = 0; i < prevBtns.length; i++){
        MN.Event.Observe(prevBtns[i], 'click', MN.TP.qmp.playlist.PageUp);
        prevBtns[i].onclick = function(){return false;}
    }
    var nextBtns = MN.GetElementsByClassName(document.body, 'mn_pl_next_page', true);
    for(var i = 0; i < nextBtns.length; i++){
        MN.Event.Observe(nextBtns[i], 'click', MN.TP.qmp.playlist.PageDown);
        nextBtns[i].onclick = function(){return false;}
    }
}

//MN.TP.PlPageChanged-----------------------------------------------------------------------//
//This is called whenever the playlist changes pages (it will automatically page up/down	//
//when a show changes to one that isn't visible in the playlist area).  The event fired		//
//passes along the page it changed to, and the total number of pages.						//
//------------------------------------------------------------------------------------------//
MN.TP.PlPageChanged = function(curPage, numOfPages){
	//write the playlist page information
    var curPageElems = MN.GetElementsByClassName(document.body, 'mn_pl_cur_page', true);
    for(var i = 0; i < curPageElems.length; i++){
        MN.SetInnerText(curPageElems[i], curPage);
    }
    var numOfPageElems = MN.GetElementsByClassName(document.body, 'mn_pl_total_pages', true);
    for(var i = 0; i < numOfPageElems.length; i++){
        MN.SetInnerText(numOfPageElems[i], numOfPages);
    }
}

//MN.TP.PlPopulated-------------------------------------------------------------------------//
//Called when the playlist populates with a new timeline.									//
//------------------------------------------------------------------------------------------//
MN.TP.PlPopulated = function(url, text){
    //Call the CustomPlPopulated function in custom.js
    if(window.CustomPlPopulated)
        CustomPlPopulated(url, text);
}

//MN.TP.InitButtons-------------------------------------------------------------------------//
//This cycles through the webpage and looks for elements with the special button classnames //
//and hooks them up to the appropriate functions.  The classes are: 						//
//"mn_back", "mn_rewind", "mn_pause", "mn_play", "mn_forward", "mn_fast_forward", 			//
//"mn_go_live", "mn_maximize", "mn_send_to_friend", "mn_pop_out", and "mn_pop_in".			//
//------------------------------------------------------------------------------------------//
MN.TP.InitButtons = function(){
    log('PAGE: Initializing buttons');
    var btns = {'back':MN.TP.Back, 'rewind':MN.TP.Rewind, 'pause':MN.TP.Pause, 'play':MN.TP.Play, 'forward':MN.TP.Forward,
                'fast_forward':MN.TP.FastForward, 'go_live':MN.TP.GoLive, 'fullscreen':MN.TP.Maximize, 'normal_size':MN.TP.NormalSize,
         				'mini':MN.TP.Minimize, 'revert':MN.TP.NormalSize};
    var curBtns;
    for(var item in btns){
        curBtns = MN.GetElementsByClassName(document.body, 'mn_' + item, true);
        for(var i = 0; i < curBtns.length; i++){
            MN.Event.Observe(curBtns[i], 'click', btns[item]);
            curBtns[i].onclick = function(){return false};
        }
    }
    log('PAGE: Done initializing buttons');
}

//MN.TP.StartEPGLoad------------------------------------------------------------------------//
//Starts loading the EPG for a channel.														//
//------------------------------------------------------------------------------------------//
MN.TP.StartEPGLoad = function(chnl){
    log('PAGE: starting load of EPG for ', chnl.name);
    if(typeof chnl.EPG == 'object')
        chnl._epg.Load(chnl.EPG);
    else if(typeof chnl.EPG == 'number')
        chnl._epg.Load({'back': chnl.EPG - 1});
    else
        chnl._epg.Load();
    //MN.Event.Observe(chnl._epg, 'urlLoaded', MN.TP.EPGLoaded);
}

//MN.TP.InitPopups--------------------------------------------------------------------------//
//Parses the page and finds anchor tags with the classname "mn_popup" and modifies them so	//
//that they open up a popup window instead of following the standard anchor tag behavior.	//
//------------------------------------------------------------------------------------------//
MN.TP.InitPopups = function(){
    log('PAGE: Initializing popups from anchor tags');
	// var anchors = document.body.getElementsByTagName('a');
	var anchors = $('mn_container').getElementsByTagName('a');
    var curAnchor;
    for(var i = 0; i < anchors.length; i++){
        curAnchor = anchors[i];
		if(curAnchor.className == 'mn_popup'){
        	var func = MN.MakeBound(curAnchor, MN.TP.PopupWin);
	        MN.Event.Observe(curAnchor, 'click', func);
	        curAnchor.onclick = function(){return false};
		}
    }
    log('PAGE: Done with popups');
}

//MN.TP.OnPlayerLoaded----------------------------------------------------------------------//
//Called when the player has finished loading.  This finishes up the rest of the page		//
//initialization, hooking up the player buttons, widgets, and events, and then begins		//
//playing video and loading the EPG for the first channel.									//
//------------------------------------------------------------------------------------------//
MN.TP.OnPlayerLoaded = function(player){
		if(player){
		log('PAGE: Player loaded');

				try {
					player.Set("MaxBitrate",750);
					player.Set("Commit","");
				} catch (e) {}

				MN.TP.onLive = true;

				MN.TP.qmp = player;
				MN.TP.InitButtons();
				MN.TP.InitEvents();
				MN.TP.InitWidgets();
				MN.TP.AdjustUI(MN.TP.curChnl);

				if(window.CustomPlayerLoaded)
						CustomPlayerLoaded(player);

				//show the playerOverlay (contains error image) if it exists -- the user has installed the player, so it's safe
				if($('playerOverlay'))
						$('playerOverlay').style.display = 'block';


				//check for send-to-friend url
				if(MN.TP.params.start && MN.TP.params.show && MN.TP.params.stop){
				log('PAGE: have send-to-friend link,attempting to play: ', MN.TP.params.show, ' start: ', MN.TP.params.start, ' stop: ', MN.TP.params.stop);
						//VOD content
						if(MN.TP.params.show.search('byid') != -1 && MN.TP.params.ep){
							//check to make sure that the episode exists
							var qvt = MN.QVT.AcquireQVT(MN.TP.params.show);
							if(qvt.IsLoading())
								MN.Event.Observe(qvt, 'TimelineLoaded', MN.TP.VODLinkLoaded)
							else
								MN.TP.VODLinkLoaded(qvt);
						}
						else{ //Live link
							MN.TP.qmp.Play(MN.TP.params.show, MN.TP.params.start, MN.TP.params.stop)
						}
				}
				else if(MN.TP.curChnl.url){
						log('PAGE: playing: ', MN.TP.curChnl.url);
						MN.TP.qmp.Play(MN.TP.curChnl.url);
				}

				//cycle through and start loading the EPGs
				var curChnl;
				for(var i = 0; i < MN.TP.CONFIG.channels.length; i++){
						curChnl = MN.TP.CONFIG.channels[i];
						if(curChnl._epg){
								MN.TP.StartEPGLoad(curChnl);
								if(MN.TP.curChnl == curChnl && MN.TP.qmp.playlist)
										MN.TP.qmp.playlist.LoadContent(curChnl._epg);
						}
				}
    }
}

MN.TP.VODLinkLoaded = function(qvt){
	MN.Event.StopObserving(qvt, 'TimelineLoaded', MN.TP.VODLinkLoaded)
	//search through the qvt to make sure that the clip still exists
	var show = -1;
	for(var i = 0; i < qvt.shows.length; i++){
		if(qvt.shows[i].id == MN.TP.params.ep){
			show = i;
			break;
		}
	}
	if(show >= 0){
		log('show is still valid, calculating start and end times from start: ', MN.TP.params.start, ' and stop: ', MN.TP.params.stop);
		MN.TP.qmp.playlist._boxTemplate = vodTemplate;
		//for VOD content, the start and stop positions are relative to the show, not timeline,
		//so calculate the position relative to the timeline
		var start = parseInt(MN.TP.params.start) + qvt.StartTime(show);
		var stop = MN.TP.params.stop == "-1" ? qvt.StopTime(show) : parseInt(MN.TP.params.stop) + qvt.StartTime(show);

		MN.TP.qmp.Play(qvt, start, stop);
	}
	else{
		//TODO: FIX!!!!!!!
		log('couldn\'t find link: ' + MN.TP.curChnl.url);
		// var newQVT = MN.QVT.AcquireQVT(MN.TP.curChnl.url);
		// 	MN.TP.Play(MN.TP.curChnl.url);
		// 	MN.TP.qmp.playlist._Populate(newQVT);
		MN.TP.qmp.Play(MN.TP.curChnl.url);
	}
}



//MN.TP.TransferPaused----------------------------------------------------------------------//
//Because the player cannot be paused before the content has loaded, we need to temporarily	//
//observe the player.PlayStateChanged event and pause when we have started playing.  This is//
//used when we are going to and from the popout player.										//
//------------------------------------------------------------------------------------------//
MN.TP.TransferPaused = function(oldPS, newPS){
    if(newPS == MN.QMP.PS.Playing){
		//we don't want to pause after every time the player starts playing, just this once
        MN.Event.StopObserving(MN.TP.qmp, 'PlayStateChanged', MN.TP.TransferPaused);
        MN.TP.Pause();
    }
}

//MN.TP.InitEvents--------------------------------------------------------------------------//
//Sets up the event observers for the player.												//
//------------------------------------------------------------------------------------------//
MN.TP.InitEvents = function(){
	log('PAGE: initializing events')

    MN.Event.Observe(MN.TP.qmp, 'PausedChanged', MN.TP.SwapPausePlay);
    MN.Event.Observe(MN.TP.qmp, 'PlayStateChanged', MN.TP.OnPlayStateChanged);
    MN.Event.Observe(MN.TP.qmp, 'TimelineLoaded', MN.TP.OnTimelineLoaded);
    MN.Event.Observe(MN.TP.qmp, 'ShowChanged', MN.TP.OnShowChanged);
    MN.Event.Observe(MN.TP.qmp, 'BitRateChanged', MN.TP.OnBitRateChanged);

    MN.Event.Observe(window, 'resize', MN.TP.OnResize);
	log('PAGE: done initializing events');
}

//MN.TP.OnBitRateChanged--------------------------------------------------------------------//
//Called when the player changes bit rates.  Updates the status message						//
//------------------------------------------------------------------------------------------//
MN.TP.OnBitRateChanged = function(newBR){
    if(MN.TP.scrubRate == 0)
      MN.TP.SetStatusMsg('Conexi\u00f3n: ' + newBR + 'kbps');
}

//MN.TP.SwapPausePlay-----------------------------------------------------------------------//
//Swaps the pause and play buttons depending on the state of the player.  When the player is//
//paused or scrubbing, we display the play button to continue playback; otherwise, the pause//
//button is showing.  This function is called both by the PausedChanged event and a few 	//
//other functions.																			//
//------------------------------------------------------------------------------------------//
MN.TP.SwapPausePlay = function(paused){
		$('mn_pause').style.display = paused ? 'none' : 'block';
		$('mn_play').style.display = paused ? 'block' : 'none';
}

//MN.TP.OnPlayStateChanged------------------------------------------------------------------//
//Called when the player changes states.  Updates the status message with the state.		//
//------------------------------------------------------------------------------------------//
MN.TP.OnPlayStateChanged = function(oldPS, newPS){
	if(!MN.TP.qmp.scrubbing){
			//translate status messages into Spanish
			if(newPS == MN.QMP.PS.Playing){
				MN.TP.SetStatusMsg('Conexi\u00f3n: ' + MN.TP.qmp.CurrentBitRate() + 'kbps');
				//make sure the pause button is now showing because we are playing
				MN.TP.SwapPausePlay(false);
				//make the duration visible
				$('mn_duration').style.textIndent = (MN.TP.onLive && MN.TP.curChnl.name.indexOf('proyecto40') != -1) ? $('mn_duration').style.textIndent : '0';
			}
			else if(newPS == (MN.QMP.PS.Loading || MN.QMP.PS.Buffering))
				MN.TP.SetStatusMsg('Cargando');
			else if(newPS == MN.QMP.PS.Opening) 		MN.TP.SetStatusMsg('Abriendo');
			else if(newPS == MN.QMP.PS.Stopped)			MN.TP.SetStatusMsg('Parado');
			else if(newPS == MN.QMP.PS.Waiting)			MN.TP.SetStatusMsg('Esperando');
			else if(newPS == MN.QMP.PS.Stalled)			MN.TP.SetStatusMsg('Cargando');//Atascando
			else if(newPS == MN.QMP.PS.Authorizing)	MN.TP.SetStatusMsg('Autorizando');
			else if(newPS == MN.QMP.PS.MediaEnded)	MN.TP.SetStatusMsg('Video Terminado');
			else if(newPS == MN.QMP.PS.Init)	MN.TP.SetStatusMsg('Inicializando');
			else
				MN.TP.SetStatusMsg(MN.QMP.PS[newPS]);

			if(newPS == MN.QMP.PS.Error) {
				$('mn_player').style.visibility = 'hidden';
				//$('errorOverlay').style.display = 'block';
				$('errorOverlay').style.left = '0px';

				if($('pageLoading').style.display == 'block')
					$('pageLoading').style.display = 'none';
			}
			else{
				if(!(MN.TP.onLive && MN.TP.curChnl.name.indexOf('proyecto40') != -1))
					$('mn_player').style.visibility = 'visible';
				//$('errorOverlay').style.display = 'none';
				$('errorOverlay').style.left = '-9000px';
			}
	}
}

//MN.TP.OnShowChanged-----------------------------------------------------------------------//
//Called when the player starts playing a different show.  Updates the show title area and	//
//adjusts the player height based on the aspect ratio of the content (unless the config		//
//specifies otherwise).																		//
//------------------------------------------------------------------------------------------//
MN.TP.OnShowChanged = function(showNum, title){
	var qvt = MN.TP.qmp.CurrentQVT();
	//sometimes the player throws an invalid show number.  This is a known, filed bug that
	//has not been fixed in the SDK yet
    if(showNum < 0 || showNum >= qvt.ShowCount()){
        showNum = qvt.ShowCount() - 1;
        title = qvt.Title(showNum);
    }
	$('mn_player').style.visibility = (MN.TP.onLive && MN.TP.curChnl.name.indexOf('proyecto40') != -1) ? 'hidden' : 'visible';

	if($('mn_show_title_center'))
		$('mn_show_title_center').innerHTML = '<b>Est\u00e1s viendo:</b> ' + '<b>' + title + '</b>' + '<b><br /><a href=http://www.chingonatv.com target=_blank>chingonatv.com</a></b>';

	if(window.CustomShowLoaded){
		CustomShowLoaded(showNum, title);
	}
}

//MN.TP.OnTimelineLoaded--------------------------------------------------------------------//
//Called when the player loads a new timeline/content to play.								//
//------------------------------------------------------------------------------------------//
MN.TP.OnTimelineLoaded = function(qvt){
}

MN.TP.AdjustUI = function(chnl){
	//set background position in JS since IE6 doesn't work if done like so:  $('mn_pl_channel').className = chnl.name;
	if(chnl.name.indexOf('estrellas') != -1)
		$('mn_pl_channel').style.backgroundPosition = '0px 0px';
	else if(chnl.name.indexOf('ciudad') != -1)
		$('mn_pl_channel').style.backgroundPosition = '0px -38px';
	else if(chnl.name.indexOf('cinco') != -1)
		$('mn_pl_channel').style.backgroundPosition = '0px -76px';
	else if(chnl.name.indexOf('galavision') != -1)
		$('mn_pl_channel').style.backgroundPosition = '0px -114px';
	else if(chnl.name.indexOf('telenovelas') != -1)
		$('mn_pl_channel').style.backgroundPosition = '0px -152px';
	else if(chnl.name.indexOf('proyecto40') != -1)
		$('mn_pl_channel').style.backgroundPosition = '0px -190px';
    else if(chnl.name.indexOf('foro') != -1)
    	$('mn_pl_channel').style.backgroundPosition = '0px -228px';

	//for proyecto40, live is disallowed; hide player, text, show static image, disable some things
	if(chnl.name.indexOf('proyecto40') != -1){
		$('mn_player').style.visibility = 'hidden';
		//$('proyectoOverlay').style.display = 'block';
		$('proyectoOverlay').style.left = '0px';
		$('mn_show_title_center').style.textIndent = '-9000px';
		$('mn_duration').style.textIndent = '-9000px';
		$('mn_status').style.textIndent = '-9000px';

		if(MN.TP.browser == "ie ie6")
			$('mn_go_live').style.display = 'none';
		else
			$('mn_go_live').className += ' disabled';
		$('mn_pause').className += ' disabled';
		$('mn_play').className += ' disabled';
		$('mn_rewind').className += ' disabled';
		$('mn_fast_forward').className += ' disabled';
		MN.TP.disableLive = true;
	}
	else{ //for other channels, make sure the elements show up
		$('mn_player').style.visibility = 'visible';
		//$('proyectoOverlay').style.display = 'none';
		$('proyectoOverlay').style.left = '-9000px';
		$('mn_show_title_center').style.textIndent = '';
		$('mn_duration').style.textIndent = '';
		$('mn_status').style.textIndent = '';

		if(MN.TP.browser == "ie ie6")
			$('mn_go_live').style.display = 'block';
		else
			$('mn_go_live').className = $('mn_go_live').className.replace('disabled','');
		$('mn_pause').className = $('mn_pause').className.replace('disabled','');
		$('mn_play').className = $('mn_play').className.replace('disabled','');
		$('mn_rewind').className = $('mn_rewind').className.replace('disabled','');
		$('mn_fast_forward').className = $('mn_fast_forward').className.replace('disabled','');
		MN.TP.disableLive = false;
	}
}

MN.TP.GetPlaylistTemplate = function(){
    var template = MN.Widget.FindNonTextChild($('mn_playlist'));
    if(!template){
        template = document.createElement('table');
        var newRow = template.insertRow(-1);
        var newCell = newRow.insertCell(-1);
        newCell.className = 'mn_show_thumb';
        newCell = newRow.insertCell(-1);
        newCell.className = 'mn_show_startTime';
        newCell = newRow.insertCell(-1);
        newCell.className = 'mn_show_title';
    }

    return template;
}

MN.TP.SwitchToChannel = function(name){
	log('switch to channel')
	var curChnl;
	for(var i = 0; i < MN.TP.CONFIG.channels.length; i++){
		curChnl = MN.TP.CONFIG.channels[i];
		if(curChnl.name == name){
			log('switch to channel')
			MN.TP.AdjustUI(curChnl);
			log('adjust ui')
			MN.TP.AdjustWidgets(curChnl);
			log('adjust widgets')
			MN.TP.curChnl = curChnl;
			log('cur chnl');
			if(MN.TP.qmp.playlist){
				log('playlist')
				if(curChnl._epg){
					log('epg')
					MN.TP.qmp.playlist.LoadContent(curChnl._epg);
				}
				else{
					log('url')
					MN.TP.qmp.playlist.LoadContent(MN.QVT.AcquireQVT(curChnl.url))
				}
			}
			MN.TP.customStartStop = true;
			MN.TP.onLive = true;
			MN.TP.stopPos = -1;
			if(MN.TP.qmp.scrubbing || MN.TP.scrubRate != 0) { //if scrubbing, pause and wait half a second
				MN.TP.Play();
				MN.TP.qmp.Play(curChnl.url);
			}
			else{
				MN.TP.qmp.Play(curChnl.url);
			}
			MN.Event.Observe(MN.TP.qmp, 'PlayStateChanged', MN.TP.ChannelSwitchPlayState);
			//if(curChnl.name.indexOf('proyecto40') != -1)
			//	MN.TP.qmp.Stop();
			return;
		}
	}
}
MN.TP.ChannelSwitchPlayState = function(oldPS, newPS){
	if(newPS == MN.QMP.PS.Playing){
		MN.Event.StopObserving(MN.TP.qmp, 'PlayStateChanged', MN.TP.ChannelSwitchPlayState);
		MN.TP.GoLive();
	}
}

MN.TP.CONFIG = {"channels":
[{"name":"","title":"",
"url":"http://1eee1b42.cdn.cms.movenetworks.com/cms/publish3/channel/qvt/8/today.qvt",
"contentType":"live","EPG":11}]};
MN.TP.InitChnlTemplates = function(chnl, plTemplate){
    if(chnl.playlist && chnl.playlist != 'hidden' && chnl.playlist.template){
        var newDiv = document.createElement('div');
        //newDiv.innerHTML = chnl.playlist.template;
        MN.SetInnerText(newDiv,chnl.playlist.template);

        chnl.playlist.template = newDiv.firstChild;
		chnl.playlist.template.style.visibility = 'visible';
    }
    else{
        if(!chnl.playlist)
            chnl.playlist = {};
        chnl.playlist.template = plTemplate;
    }
}

MN.TP.GetTimeFormats = function(){
    var timeFormats = {};

    if($('mn_duration')){
        var formats = $('mn_duration').innerHTML.split('/');
        timeFormats.tlPos = formats[0] || 'HH:MM:SS';
        timeFormats.tlDur = formats[1] || 'HH:MM:SS';
        //$('mn_duration').innerHTML = '';
        MN.SetInnerText($('mn_duration'),'');
    }
    if($('mn_timeline')){
        timeFormats.scrubPos = $('mn_timeline_scrubber').innerHTML || 'HH:MM:SS';
        //$('mn_timeline_scrubber').innerHTML = '';
        MN.SetInnerText($('mn_timeline_scrubber'),'');
    }

    return timeFormats;
}

MN.TP.InitChannels = function(){
    log('PAGE: Initializing channels');
    var curChnl = {};

    if($('mn_playlist'))
        var plTemplate = MN.TP.GetPlaylistTemplate();
    var timeFormats = MN.TP.GetTimeFormats();
    if(MN.TP.CONFIG){
        MN.TP.curChnl = MN.TP.CONFIG.channels[0];
        for(var i = 0; i < MN.TP.CONFIG.channels.length; i++){
            curChnl = MN.TP.CONFIG.channels[i];
            //create a channel name if there isn't one
            if(!curChnl.name)
	            curChnl.name = 'chnl_' + i;

	        log('PAGE: Initializing channel: ', curChnl.name);
	        //calculate the url
	        MN.TP.InitChnlURL(curChnl);
            //prepare the various templates and timeFormats
            MN.TP.InitChnlTemplates(curChnl, plTemplate);
            MN.TP.InitChnlTimeFormats(curChnl, timeFormats);
            //create an EPG
            if(curChnl.EPG){
                curChnl._epg = new MN.EPG(curChnl.url, curChnl.name, curChnl.contentType);
            }

            if(MN.TP.params.chnl == curChnl.name) //send-to-a-friend link, need to set MN.TP.curChnl to proper channel
	            MN.TP.curChnl = curChnl;

            log('PAGE: Done initializing channel: ', curChnl.name);
        }

    }
    else{
        curChnl.name = 'chnl_0';

        MN.TP.InitChnlTemplates(curChnl, plTemplate);
        MN.TP.InitChnlTimeFormats(curChnl, timeFormats);
        MN.TP.curChnl = curChnl;
    }

    log('PAGE: Done initializing channels');
}

MN.TP.InitChnlTimeFormats = function(chnl, timeFormats){
    if(!chnl.timeline)
        chnl.timeline = {};

    if(chnl.timeline != 'hidden'){
        chnl.timeline.posFormat = chnl.timeline.posFormat || timeFormats.tlPos;
        chnl.timeline.durFormat = chnl.timeline.durFormat || timeFormats.tlDur;
        chnl.timeline.scrubFormat = chnl.timeline.scrubFormat || timeFormats.scrubPos;
    }
}

MN.TP.InitChnlURL = function(chnl){
    if(chnl.url){
        //calculate absolute url from relative url if we have one (the player requires a full url to play)
		if(chnl.url.search('http://') == -1){
			chnl.url = MN.URL.Join(window.location.href, chnl.url);
		}
    }
    else if(chnl.baseURL)
        chnl.url = MN.TP.CreateQVTURL(chnl.baseURL, chnl.timezone);
}

MN.TP.CreateQVTURL = function(baseURL, timezone){
	var msInHour = 3600000;
	var url;
	var today_date = new Date();
	var todayInMS = today_date.getTime();
	todayInMS += (timezone || 0) * msInHour;
	today_date.setTime(todayInMS);

	fDate = MN.TP.FormattedDateObject(today_date);
	var year = String(fDate.year).substr(2,2);

	url = (baseURL || '') + year + fDate.month + fDate.day + '.qvt';
	log('PAGE: Created today\'s url: ', url);
	return url;
}

MN.TP.FormattedDateObject = function(date){
	//For day of week 1=Sunday, For month 1=January
	return {year:date.getUTCFullYear(), month:MN.PadDigits(date.getUTCMonth()+1, 2),
			day:MN.PadDigits(date.getUTCDate(), 2), hour:MN.PadDigits(date.getUTCHours(), 2),
			minute:MN.PadDigits(date.getUTCMinutes(), 2), second:MN.PadDigits(date.getUTCSeconds(), 2),
			dow:date.getUTCDay()+1};
}

MN.TP.WindowLoaded = function(){
	MN.TP.SetScreenResolution();
	//if(MN.nonIE)
	//	window.resizeTo(830,565);
	//else
	//	window.resizeTo(830,585);
	window.resizeTo(MN.TP.screenWidth,MN.TP.screenHeight);

    MN.TP.params = MN.GetPageParams();
    if(MN.TP.params.debug)
        MN.Log.ShowPane(500);

    MN.TP.InitPopups();
    MN.TP.CustomizeShowTl();
    MN.TP.InitChannels();
	MN.TP.OnResize();

	if(window.CustomWindowLoaded){
		CustomWindowLoaded();
	}

	log('new new')
	$('pageLoading').style.display = 'none';
	log('PAGE: all good, call MN.QVT.CreatePlayer');
	MN.QVT.CreatePlayer('mn_player', MN.TP.OnPlayerLoaded, '100%', '100%');
}

MN.TP.SetScreenResolution = function() {
	if (screen.height >= 1024) {
		MN.TP.screenWidth = 1062;
		MN.TP.screenHeight = 853;
		document.getElementsByTagName('body')[0].className += 's1024';
	}
	else if(screen.height >= 768) {
		MN.TP.screenWidth = 807;
		MN.TP.screenHeight = 598;
	}
	else {
		MN.TP.screenWidth = 583;
		MN.TP.screenHeight = 430;
	}
}

MN.Event.Observe(window, 'load', MN.TP.WindowLoaded);
MN.Event.Observe(window, 'scroll', function() {
		//Nate's hackfix to minimize or eliminate player artifacts caused by excessive scrolling
		document.body.style.backgroundColor = '#343b40';
		document.body.style.backgroundColor = '#343b41'; //the correct bgcolor

		//Ben's hackfix for the Vista 'player can leave my browser window' problem
		if (navigator.userAgent.indexOf("Windows NT 6.0") > -1) {
				$('mn_player').style.visibility = 'hidden';
				setTimeout("$('mn_player').style.visibility = 'visible'",1); //wait a millisecond, else it doesn't work
		}
} );

// CSS Browser Selector   v0.2.5
// Documentation:         http://rafael.adm.br/css_browser_selector
// License:               http://creativecommons.org/licenses/by/2.5/
// Author:                Rafael Lima (http://rafael.adm.br)
// Contributors:          http://rafael.adm.br/css_browser_selector#contributors
var css_browser_selector = function() {
	var
		ua=navigator.userAgent.toLowerCase(),
		is=function(t){ return ua.indexOf(t) != -1; },
		h=document.getElementsByTagName('html')[0],
		b=(!(/opera|webtv/i.test(ua))&&/msie (\d)/.test(ua))?('ie ie'+RegExp.$1):is('gecko/')? 'gecko':is('opera/9')?'opera opera9':/opera (\d)/.test(ua)?'opera opera'+RegExp.$1:is('konqueror')?'konqueror':is('applewebkit/')?'webkit safari':is('mozilla/')?'gecko':'',
		os=(is('x11')||is('linux'))?' linux':is('mac')?' mac':is('win')?' win':'';
	var c=b+os+' js';
	MN.TP.browser = b;
	h.className += h.className?' '+c:c;
}();

